#!/usr/bin/env bash

################################################################################
#                                                                              #
# hypertorify.sh                                                               #
#                                                                              #
# version: alpha                                                               #
#                                                                              #
# Hyperbola v0.4 - Transparent proxy through Tor                               #
#                                                                              #
# Copyright (C) 2021 Sagar Acharya                                             #
#                                                                              #
#                                                                              #
# GNU GENERAL PUBLIC LICENSE                                                   #
#                                                                              #
# This program is free software: you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License as published by         #
# the Free Software Foundation, either version 3 of the License.               #
#                                                                              #
# This program is distributed in the hope that it will be useful,              #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with this program.  If not, see <http://www.gnu.org/licenses/>.        #
################################################################################

## General
#
# program information
readonly prog_name="hypertorify"
readonly version="alpha"
readonly signature="Copyright (C) 2021 Sagar Acharya"
readonly git_url="https://notabug.org/sagaracharya/hypertorify"

# set colors for stdout
export red="$(tput setaf 1)"
export green="$(tput setaf 2)"
export yellow="$(tput setaf 3)"
export blue="$(tput setaf 4)"
export magenta="$(tput setaf 5)"
export cyan="$(tput setaf 6)"
export white="$(tput setaf 7)"
export b="$(tput bold)"
export reset="$(tput sgr0)"

## Directories
#
readonly app_dir="/usr/share/hypertorify"

## Show program banner
banner() {
printf "${b}${cyan}
 _     _ __   __  _____  _______  ______ _______  _____   ______ _____ _______ __   __
 |_____|   \_/   |_____] |______ |_____/    |    |     | |_____/   |   |______   \_/  
 |     |    |    |       |______ |    \_    |    |_____| |    \_ __|__ |          |   

v${version}
=[ Transparent proxy through Tor
${reset}\\n\\n"
}


## Print a message and exit with (1) when an error occurs
die() {
    printf "${red}%s${reset}\\n" "[ERROR] ${@}" >&2
    exit 1
}

## Print information
info() {
    printf "${b}${cyan}%s${reset} ${b}%s${reset}\\n" "::" "${@}"
}


## Print `OK` messages
msg() {
    printf "${b}${green}%s${reset} %s\\n\\n" "[OK]" "${@}"
}

## Check if the program run as a root
check_root() {
    if [[ "${UID}" -ne 0 ]]; then
        die "Please run this program as a root!"
    fi
}

## Display program version
print_version() {
    printf "%s\\n" "${prog_name} ${version}"
    printf "%s\\n" "${signature}"
    printf "%s\\n" "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>"
    printf "%s\\n" "This is free software: you are free to change and redistribute it."
    printf "%s\\n" "There is NO WARRANTY, to the extent permitted by law."
    exit 0
}

## Check program settings
#
# - tor package
# - tor configuration file: /etc/tor/torrc
# - directory permissions: /var/lib/tor
check_settings() {
    info "Check program settings"

    if ! hash tor 2>/dev/null; then
        die "tor isn't installed, exit"
    fi

    if [[ ! -d "${app_dir}/backups" ]]; then
        die "directory '${app_dir}/backups' not exist, run makefile first!"
    fi

    # check /var/lib/tor permissions
    #
    # required:
    # -rwx------  tor tor
    # (700)
    if [[ "$(stat -c '%U' /var/lib/tor)" != "tor" ]] &&
        [[ "$(stat -c '%a' /var/lib/tor)" != "700" ]]; then

        printf "%s\\n" "Set permissions of /var/lib/tor directory"
        chown -R tor:tor /var/lib/tor
        chmod -R 700 /var/lib/tor
    fi

    if [[ ! -f /etc/init.d/tor-hardened ]]; then
        die "Failed to find /etc/init.d/tor-hardened. Please install package tor-hardened-preferences."
    fi

    # check /etc/tor/torrc
    if [[ ! -f /etc/tor/torrc ]]; then
        die "/etc/tor/torrc file not exist, check Tor configuration"
    fi
}

## nftables settings
#
# This function is used with args in start() & stop() for set/restore
# nftables.
#
# Args:
#       tor_proxy -> set rules for Tor transparent proxy
#       default   -> restore default nftables
setup_nftables() {
    case "$1" in
        tor_proxy)
            printf "%s\\n" "Set nftables rules"

            ## flush current nftables rules

            #nft create table inet nat
            #nft create table inet filter

            #nft create chain inet filter input
            #nft add rule inet filter input accept
            #nft create chain inet filter forward
            #nft add rule inet filter forward accept
            #nft create chain inet filter output
            #nft add rule inet filter output accept

            # copy file /usr/share/hypertorify/data/nftables.rules in the
            # /etc/ directory

            if ! mv -f /etc/nftables.rules "${app_dir}/backups/nftables.rules.backup" 2>/dev/null; then
                die "can't backup file /etc/nftables.rules to hypertorify backup directory"
            fi
            
            if ! cp -f "${app_dir}/ruleset.nft" /etc/nftables.rules 2>/dev/null; then
                die "can't copy file /etc/nftables.rules"
            fi

            # set new nftables rules
            
            if nft -f < /etc/nftables.rules 2>/dev/null; then
                die "can't set nftables rules"
            fi

            # start nftables service
            if ! rc-service nftables start 2>/dev/null; then
                die "openrc error, exit!"
            fi
        ;;

        # restore default
        default)
            printf "%s\\n" "Restore default nftables"

            # flush nftables rules
            nft flush ruleset
            
            nft create table inet nat
            nft create table inet filter

            nft create chain inet filter input
            nft add rule inet filter input accept
            nft create chain inet filter forward
            nft add rule inet filter forward accept
            nft create chain inet filter output
            nft add rule inet filter output accept

            # rewrite default /etc/nftables.rules file
            if ! mv -f "${app_dir}/backups/nftables.rules.backup" /etc/nftables.rules 2>/dev/null; then
                die "can't restore backup nftables.rules.backup file from hypertorify backup directory"
            fi
        ;;
    esac
}

## Check public IP address
#
# Make an HTTP request to the URL in the list, if the first request fails, try
# with the next, then print the IP address.
#
# Thanks to "NotMilitaryAI" for this function
check_ip() {
    info "Check public IP Address"

    local url_list=(
        'https://ipleak.net/json/'
        'https://api.myip.com/'
    )

    for url in "${url_list[@]}"; do
        local request="$(curl -s "$url")"
        local response="$?"

        if [[ "$response" -ne 0 ]]; then
            continue
        fi

        printf "%s\\n" "${request}"
        break
    done
}


## Check status of program and services
#
# - tor-hardened
# - tor settings (check if Tor works correctly)
# - public IP address
check_status() {
    info "Check current status of Tor service"

    if rc-service tor-hardened status | grep started >/dev/null 2>&1; then
        msg "Tor service is active"
    else
        die "Tor service is not running! exit"
    fi

    # make HTTP request with curl at: https://check.torproject.org/
    # and grep the necessary strings from the html page to test connection
    # with tor
    info "Check Tor network settings"

    # curl option details:
    #   --socks5 <host[:port]> SOCKS5 proxy on given host + port
    #   --socks5-hostname <host[:port]> SOCKS5 proxy, pass host name to proxy
    #
    #   `-L` and `tac` for avoid error: "(23) Failed writing body"
    #   https://github.com/kubernetes/helm/issues/2802
    #   https://stackoverflow.com/questions/16703647/why-curl-return-and-error-23-failed-writing-body

    if curl -s -m 5 --socks5 "localhost:9050" --socks5-hostname "localhost:9050" -L "https://check.torproject.org/" \
        | cat | tac | grep -q 'Congratulations'; then
        printf "${b}${green}%s${reset} %s\\n\\n" \
                "[OK]" "Your system is configured to use Tor"
    else
        printf "${red}%s${reset}\\n\\n" "[!] Your system is not using Tor"
        printf "%s\\n" "try another Tor circuit with '${prog_name} --restart'"
        exit 1
    fi

    check_ip
}

## Start transparent proxy through Tor
start() {
    check_root

    # Exit if tor is already active
    if rc-service tor status | grep started >/dev/null 2>&1; then
        die "Tor service is already active, stop it first"
    fi

    banner
    sleep 2
    check_settings

    printf "\\n"
    info "Starting Transparent Proxy"

    # DNS settings: /etc/resolv.conf:
    #
    # write nameserver 127.0.0.1 to etc/resolv.conf file
    # i.e. use Tor DNSPort (see: /etc/tor/torrc)
    printf "%s\\n" "Configure DNS to use Tor's DNSPort"

    # backup current resolv.conf
    if ! cp /etc/resolv.conf "${app_dir}/backups/resolv.conf.backup"; then
        die "can't backup /etc/resolv.conf"
    fi

    # write new nameserver
    printf "%s\\n" "nameserver 127.0.0.1" > /etc/resolv.conf

    # restart tor-hardened
    printf "%s\\n" "Starting tor-hardened"

    if ! rc-service tor-hardened status | grep started >/dev/null 2>&1; then
        if ! rc-service tor-hardened start 2>/dev/null; then
            die "can't start tor-hardened, exit!"
        else
            echo "started tor-hardened"
    	fi
    fi

    # nftables settings
    setup_nftables tor_proxy

    # check program status
    printf "\\n"
    check_status

    printf "\\n${b}${green}%s${reset} %s\\n" \
            "[OK]" "Transparent Proxy activated, your system is under Tor"
}


## Stop transparent proxy
#
# stop connection with Tor Network and return to clearnet navigation
stop() {
    check_root

    # don't run function if tor-hardened is NOT running!
    if rc-service tor-hardened status | grep started >/dev/null 2>&1; then
        info "Stopping Transparent Proxy"

        # restore default nftables and stop tor-hardened and nftables services
        setup_nftables default
        
        printf "%s\\n" "Stop Tor service"
        rc-service tor-hardened stop
        rc-service nftables stop

        # restore /etc/resolv.conf:
        #
        # restore file with resolvconf program if exists, otherwise copy the
        # original file from backup directory
        printf "%s\\n" "Restore default DNS"

        if hash resolvconf 2>/dev/null; then
            resolvconf -u
        else
            cp "${app_dir}/backups/resolv.conf.backup" /etc/resolv.conf
        fi

        # restore default /etc/tor/torrc
        printf "%s\\n" "Restore default /etc/tor/torrc"
        cp "${app_dir}/backups/torrc.backup" /etc/tor/torrc

        printf "\\n${b}${green}%s${reset} %s\\n" \
                "[-]" "Transparent Proxy stopped"
        exit 0
    else
        die "Tor-hardened service is not running! exit"
    fi
}


## Restart
#
# restart tor-hardened (i.e. get new Tor exit node)
# and change public IP address
restart() {
    check_root

    if rc-service tor-hardened status | grep started >/dev/null 2>&1; then
        info "Change IP address"

        rc-service tor-hardened restart
        rc-service nftables restart
        sleep 1
        msg "IP address changed"
        check_ip
        exit 0
    else
        die "Tor service is not running! exit"
    fi
}


## Show help menù
usage() {
    printf "%s\\n" "${prog_name} ${version}"
    printf "%s\\n" "Hyperbola OS - Transparent proxy through Tor"
    printf "%s\\n\\n" "${signature}"

    printf "%s\\n\\n" "Usage: ${prog_name} [option]"

    printf "%s\\n\\n" "Options:"

    printf "%s\\n" "-h, --help      show this help message and exit"
    printf "%s\\n" "-t, --tor       start transparent proxy through tor"
    printf "%s\\n" "-c, --clearnet  reset nftables and return to clearnet navigation"
    printf "%s\\n" "-s, --status    check status of program and services"
    printf "%s\\n" "-i, --ipinfo    show public IP address"
    printf "%s\\n" "-r, --restart   restart tor service and change IP address"
    printf "%s\\n\\n" "-v, --version   display program version and exit"

    printf "%s\\n" "Project URL: ${git_url}"
    printf "%s\\n" "Report bugs: ${git_url}/issues"

    exit 0
}

## Main function
#
# parse command line arguments and start program
main() {
    if [[ "$#" -eq 0 ]]; then
        printf "%s\\n" "${prog_name}: Argument required"
        printf "%s\\n" "Try '${prog_name} --help' for more information."
        exit 1
    fi

    while [[ "$#" -gt 0 ]]; do
        case "$1" in
            -t | --tor)
                start
                ;;
            -c | --clearnet)
                stop
                ;;
            -r | --restart)
                restart
                ;;
            -s | --status)
                check_status
                ;;
            -i | --ipinfo)
                check_ip
                ;;
            -v | --version)
                print_version
                ;;
            -h | --help)
                usage
                exit 0
                ;;
            -- | -* | *)
                printf "%s\\n" "${prog_name}: Invalid option '$1'"
                printf "%s\\n" "Try '${prog_name} --help' for more information."
                exit 1
                ;;
        esac
        exit 0
    done
}

# Call main
main "${@}"
